import base64
import socket
import sys
import time

from utils import load_config, send_msg, server_response, print_to_server, parse_cmd, commands, print_to_client, \
    save_mail, is_cmd_valid, generate_nonce, generate_hmac_value

# import logging
# logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s',
#                      level=logging.DEBUG)
PERSONAL_ID ='C8BB1A'
PERSONAL_SECRET = '4acb39586b46d8455b20ebfce57b46cd'


def check_valid(state, cmd):
    if cmd == commands.MAIL.value:
        if len(state) ==0 :return  False
        if state[-1] not in [commands.QUIT.value,commands.EHLO.value,commands.REST.value]:
            return False
    elif cmd == commands.RCPT.value:
        if len(state) == 0:return False
        if state[-1] not in [commands.RCPT.value,commands.MAIL.value]:
            return False
    elif cmd == commands.DATA.value:
        if len(state) ==0:return False
        if state[-1] not in [commands.RCPT.value]:
            return False
    elif cmd == commands.DATA_BODY.value:
        if len(state) == 0 :return False
        if state[-1] not in [commands.DATA_BODY.value,commands.DATA.value]:
            return False
    elif cmd == commands.DATA_END.value:
        if len(state) == 0: return False
        if state[-1] not in [commands.DATA_BODY.value,commands.DATA.value]:
            return False
    elif cmd == commands.QUIT.value:
        pass
    elif cmd == commands.REST.value:
        pass
    elif cmd == commands.AUTH.value:
        if len(state) and  state[-1] not in [commands.REST.value,commands.DATA_END.value,commands.EHLO.value,commands.AUTH_CHALLENGE.value]:
            return False
    elif cmd == commands.AUTH_CHALLENGE.value:
        if len(state) == 0:return False
        if state[-1] != commands.AUTH.value:
            return False
    state.append(cmd)
    return True


class Server():
    def __init__(self,path):
        try:
            config = load_config(path)
            self.server_port = config['DEFAULT']['server_port']
            self.host = '127.0.0.1'
            self.inbox = config['DEFAULT']['inbox_path']
        except Exception:
            exit(2)
        self.name = PERSONAL_ID
        self.secret = PERSONAL_SECRET
        self.auth = False
        self.start_server()

    def start_server(self):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
            server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            server.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            server.bind((self.host,int(self.server_port)))
            server.listen()
            while True:
                try:
                    conn,addr = server.accept()
                    send_msg(conn,server_response.re_220.value)
                    print_to_server(server_response.re_220.value)
                except Exception as e :
                    print(e)
                    print_to_server(f"Connection lost")
                state = []
                mail_buffer = []
                while True:
                    try:
                        response = conn.recv(1024).decode()
                        if not response:
                            print_to_server("Connection lost")
                            break
                    except Exception as e :
                        print_to_server("Connection lost")
                        continue
                    cmd,parameter,res = parse_cmd(response,state[-1] if len(state) else None)
                    print_to_client(response)
                    try:
                        # 先检查 语句状态在检查 是否合法?
                        if not check_valid(state,cmd):
                            send_msg(conn,server_response.re_503.value)
                            print_to_server(server_response.re_503.value)
                            continue

                        if not is_cmd_valid(cmd,parameter,response):
                            send_msg(conn,server_response.re_501.value)
                            print_to_server(server_response.re_501.value)
                            state.pop(-1)
                            continue
                        if cmd == commands.EHLO.value:
                            send_msg(conn,f"250 {self.host}\r\n250 {commands.AUTH.value} CRAM-MD5")
                            print_to_server(f"250 {self.host}")
                            print_to_server(f"250 {commands.AUTH.value} CRAM-MD5")
                        elif cmd == commands.MAIL.value:
                            mail_buffer.append((cmd,parameter))
                            send_msg(conn,server_response.re_250.value)
                            print_to_server(server_response.re_250.value)

                        elif cmd == commands.RCPT.value:
                            mail_buffer.append((cmd,parameter))
                            send_msg(conn,server_response.re_250.value)
                            print_to_server(server_response.re_250.value)
                        elif cmd == commands.DATA.value:
                            send_msg(conn,server_response.re_354.value)
                            print_to_server(server_response.re_354.value)

                        elif cmd == commands.DATA_BODY.value:
                            # TODO: 在发email的时候,其他指令被视为是body?no Manual input accept ?
                            mail_buffer.append((cmd,parameter))
                            send_msg(conn,server_response.re_354.value)
                            print_to_server(server_response.re_354.value)

                        elif cmd == commands.DATA_END.value:
                            send_msg(conn,server_response.re_250.value)
                            print_to_server(server_response.re_250.value)

                        elif cmd == commands.REST.value:
                            mail_buffer.clear()
                            state.clear()
                            state.append(commands.REST.value)
                            send_msg(conn,server_response.re_250.value)
                            print_to_server(server_response.re_250.value) # 补上Rest 的返回值 250
                        elif cmd == commands.QUIT.value:
                            if len(state)>2 and state[-2] == commands.DATA_END.value: # 如果是QUIT指令,且此时前一位是END时候,发送邮件
                                save_mail(self.inbox,mail_buffer)
                            mail_buffer.clear()
                            state.clear()
                            send_msg(conn,server_response.re_221.value)
                            print_to_server(server_response.re_221.value)
                            break
                        elif cmd == commands.AUTH.value:
                            if parameter != "CRAM-MD5":
                                print_to_server(server_response.re_504.value)
                                send_msg(conn,server_response.re_504.value)
                                state.pop(-1)
                            else:
                                self.nonce = generate_nonce()
                                send_msg(conn,f"{server_response.re_334.value} {self.nonce.decode()}")
                                print_to_server(f"{server_response.re_334.value} {self.nonce.decode('utf-8')}")
                        elif cmd == commands.AUTH_CHALLENGE.value:
                            challenge = parameter
                            if parameter == '*':
                                send_msg(conn,server_response.re_501.value)
                                print_to_server(server_response.re_501.value)
                                continue
                            try:
                                ans = base64.b64decode(challenge.encode()).decode('utf-8').split()
                            except Exception as e :
                                send_msg(conn,server_response.re_501.value)
                                print_to_server(server_response.re_501.value)
                                continue
                            if generate_hmac_value(self.secret,self.nonce) == ans[1]:
                                send_msg(conn,server_response.re_235.value)
                                print_to_server(server_response.re_235.value)
                            else:
                                send_msg(conn,server_response.re_535.value)
                                print_to_server(server_response.re_535.value)

                        elif cmd == commands.NOOP.value:
                            # TODO:// NOOP 不影响整个state 的判断
                            if len(state) and state[-1] == commands.NOOP.value:state.pop(-1)
                            send_msg(conn,server_response.re_250.value)
                            print_to_server(server_response.re_250.value)
                    except BrokenPipeError:
                        print_to_server("Connection lost")
                        break













def main(argv):
    if len(argv)<2:
        exit(1)
    Server(argv[1])



if __name__ == '__main__':

    main(sys.argv)